/*
 * FeedExpress
 * (C) Copyright 2003 Jeppe Cramon (jeppe@cramon.dk)
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Disclaimer:
 * -----------
 * This software is provided "as is" without warranty of any kind,
 * either expressed or implied. The entire risk as to the
 * quality and performance of the software is with you. Should the
 * software prove defective, you assume the cost of all necessary
 * servicing, repair, or correction. In no event shall the author,
 * copyright holder, or any other party who may redistribute the
 * software be liable to you for damages, including any general,
 * special, incidental, or consequential damages arising out of
 * the use or inability to use the software (including, but not
 * limited to, loss of data, data being rendered inaccurate, loss of
 * business profits, loss of business information, business
 * interruptions, loss sustained by you or third parties, or a
 * failure of the software to operate with any other software) even
 * if the author, copyright holder, or other party has been advised
 * of the possibility of such damages. 
 * 
 */

using System;
using System.Collections;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Remoting.Messaging;
using System.IO;
using System.Runtime.CompilerServices;

namespace Pbdms.BusinessLogic.Mail
{
	/// <summary>
	/// Specific CacheHandler for handling feeds.
	/// All items that are cached, are required to implement the ICacheable interface
	/// </summary>
	public class MessageCache : Common.Interfaces.ICache
	{
		private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		private Uri mCacheUri;
		// Store all subscriptions.url as key and save all feeds as an value in form of a IDictionary
		private Hashtable mInternalCache;

		/// <summary>
		/// Creates a new cache and loads the cache from the destination appointed by the Uri. 
		/// Only file caches are supported
		/// </summary>
		/// <param name="uri"></param>
		public MessageCache(Uri uri)
		{
			if (!uri.IsFile) {
				throw new NotSupportedException("Only supports File based caching. Received the following Uri: " + uri.ToString());
			}
			mCacheUri = uri;
			loadCache();
		}

		/// <summary>
		/// Returns all FeedEntries in an IDictionary (Hashtable)
		/// </summary>
		/// <param name="key">The subscription's key (Subscription.cachekey)</param>
		/// <returns>Returns all FeedEntries in an IDictionary</returns>
		[MethodImpl(MethodImplOptions.Synchronized)]
        public ICollection getAll(Common.Interfaces.ICacheable objKey)
        {
			ICollection returnValue = (ICollection) mInternalCache[objKey.cacheKey];
			if (returnValue == null) {
				returnValue = new Hashtable();
				mInternalCache.Add(objKey.cacheKey, returnValue);
			}
			return returnValue;
			
		}

		/// <summary>
		/// Update all messages
		/// </summary>
		/// <param name="key">The subscription's key (Subscription.cachekey)</param>
		/// <param name="objects">IDictionary of FeedEntries</param>
		/// <returns>true if any new feeds were added, otherwise false</returns>
		[MethodImpl(MethodImplOptions.Synchronized)]
        public bool updateAll(Common.Interfaces.ICacheable mailBoxObj, ICollection objects)
        {
			bool bWasUpdated = false;
            IDictionary messages = (IDictionary)mInternalCache[mailBoxObj.cacheKey];
            if (messages == null)
            {
                messages = new Hashtable();
                mInternalCache.Add(mailBoxObj.cacheKey, messages);
			}
            MailBox mailBox = (MailBox)mailBoxObj;
			IDictionary newMessages = (IDictionary)objects;
            foreach (String messageKey in newMessages.Keys)
            {
                if (!messages.Contains(messageKey))
                {
					// Add the new message

					bWasUpdated = true;
                    messages.Add(messageKey, newMessages[messageKey]);
				}
			}
			return bWasUpdated;
		}

		/// <summary>
		/// Remove a subscription
		/// </summary>
		/// <param name="key">The subscription's key (Subscription.cachekey)</param>
		[MethodImpl(MethodImplOptions.Synchronized)]
        public void remove(Common.Interfaces.ICacheable objKey)
        {
			mInternalCache.Remove(objKey.cacheKey);
		}


		/// <summary>
		/// Clears all feeds for this subscription
		/// </summary>
		/// <param name="key">The subscription's key (Subscription.cachekey)</param>
		[MethodImpl(MethodImplOptions.Synchronized)]
        public void clear(Common.Interfaces.ICacheable objKey)
        {
			((IDictionary)mInternalCache[objKey.cacheKey]).Clear();
		}

		/// <summary>
		/// Remove a specific feed
		/// </summary>
		/// <param name="key">The subscription's key (Subscription.cachekey)</param>
		/// <param name="obj">FeedEntry key (FeedEntry.cacheKey)</param>
		[MethodImpl(MethodImplOptions.Synchronized)]
        public void remove(Common.Interfaces.ICacheable objKey, Common.Interfaces.ICacheable objValue)
        {
			((IDictionary)mInternalCache[objKey.cacheKey]).Remove(objValue.cacheKey);
		}

		/// <summary>
		/// Clears the internal cache
		/// </summary>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void clear() {
			mInternalCache.Clear();
		}

		/// <summary>
		/// loads the cache from the localtion specified by the uri property. Only file based cache is supported
		/// </summary>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void loadCache() {
			if (log.IsDebugEnabled) {
				log.Debug("loading Cache from " + mCacheUri.ToString());
			}

			if (File.Exists(mCacheUri.LocalPath)) {
				try {
					IRemotingFormatter xs = new BinaryFormatter();
					using (FileStream fs = new FileStream(mCacheUri.LocalPath, FileMode.Open)) {
						Hashtable tmpDic = (Hashtable)xs.Deserialize(fs);
						mInternalCache = Hashtable.Synchronized( tmpDic );
					}
				} catch (Exception e) {
					log.Error("FeedCache (" + mCacheUri.ToString() + ") is corrupted. Deleting the old cache", e);
					try {
						File.Delete(mCacheUri.LocalPath);
					} catch (Exception e2) {
						log.Debug("Deletion of Feed Cache file " + mCacheUri.ToString() + " failed.", e2);
					}
					mInternalCache = new Hashtable();
				}
			} else {
				if (log.IsDebugEnabled) {
					log.Debug("Cache file " + mCacheUri.ToString() + " didn't exist, creating default cache");
				}
				mInternalCache = new Hashtable();
			}

			if (log.IsDebugEnabled) {
				log.Debug("Done loading cache");
			}
		}

		/// <summary>
		/// Saves the cache to the location specified by the uri property. Currently only file based caching is supported
		/// </summary>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void saveCache() {
			if (log.IsDebugEnabled) {
				log.Debug("saving Cache to " + mCacheUri.ToString());
			}
			try {
				IRemotingFormatter xs = new BinaryFormatter();
				using (FileStream fs = new FileStream(mCacheUri.LocalPath, FileMode.Create)) {
					xs.Serialize(fs, mInternalCache);
				}
			} catch (Exception e) {
				log.Error("Failed to save cache", e);
			}
			if (log.IsDebugEnabled) {
				log.Debug("Done saving Cache");
			}
		}

		/// <summary>
		/// The location of the cache. Only file based caching is supported.
		/// </summary>
		public Uri cacheUri {
			get { return mCacheUri; }
		}
	}
}
